package processors;

import java.util.ArrayList;
import java.util.Arrays;

import types.ApproximData;
import utilities.MathUtils;
import utilities.Printers;

public class ApproximDataProcessor
{
	
	public static ApproximData buildApproxim(
			int[][] imgData
			
	) {
		final int imgHeight = imgData.length;
		final int imgWidth = imgData[0].length;
		ApproximData approxim = new ApproximData( imgHeight, imgWidth );
		int[][] imgMap = approxim.imgMap();

		int[] curSetsCount = new int[] { 0 };
		int[] setsCache = new int[4];
		
		// init zero set
		imgMap[0][0] = 0;
		ArrayList<double[][]> systems = approxim.systems();
		
		// process all points of the map
		for ( int row = 0; row < imgHeight; ++row )
		{
			for ( int col = 0; col < imgWidth; ++col )
			{
				processPoint(
						row,
						col,
						imgHeight,
						imgWidth,
						imgMap,
						imgData,
						systems,
						curSetsCount,
						setsCache
				);
				System.out.println( "============" ); 
				Printers.printSystem( systems.get( 0 ), System.out );
			}
		}
		return approxim;
	} // static ApproximData buildApproxim
	private static int processPoint(
			final int row,
			final int col,
			final int imgHeight,
			final int imgWidth,
			int[][] imgMap,
			int[][] imgData,
			ArrayList<double[][]> systems,
			int[] curSetsCount,
			int[] setsCache
	) {
		final int color = imgData[row][col];
		int r1, c1, curNum=0, setNum, i, checkRes;
		for ( byte[] offset : neighborsOffsets )
		{
			r1 = row + offset[0];
			c1 = col + offset[1];
			if ( r1 < 0 || c1 < 0 || c1 >= imgWidth )
				continue; // neighbor point not in map borders, skip
			setNum = imgMap[r1][c1];
			// scan sets nums cache, if this set already checked
			for ( i = 0; i < curNum; ++i )
				if ( setNum == setsCache[i] )
					break; // set already checked
			if ( i == curNum ) // set not checked
			{
				setsCache[curNum++] = setNum;
				/*imgMap[row][col] = setNum;
				// add point to set
				addPointToApproximateSystem(
						systems.get( setNum ),
						row,
						col,
						imgData[row][col]
				);*/
				checkRes = checkNeighborSet(
						setNum,
						systems,
						row,
						col,
						color
				);
				if ( checkRes == 0 )
				{
					// point can be added to set, system already modifyed
					imgMap[row][col] = setNum;
					return setNum;
				}
				else
				{
					
				}
				return 0;
				//break; // skip checking other neighbors
			} // if ( i == curNum )
		} // for offset
		
		// if here: point was not added to any neighbor set
		double[][] system = createApproximateSystem( row, col, imgData[row][col] );
		systems.add( system );
		imgMap[row][col] = curSetsCount[0];
		curSetsCount[0]++;
		
		return -1;
	} // int processPointd
	/**
	 * checks point to be added to set. Also writes new system to systems set
	 * NB! do not modify map
	 * @param setNum
	 * @param systems
	 * @param point
	 * @return 0, if point was added to set, -1 else
	 */
	private static int checkNeighborSet(
			final int setNum,
			ArrayList<double[][]> systems,
			final int x,
			final int y,
			final int color
	) {
		double[][] system = systems.get( setNum );
		final int h = system.length;
		double[][] sys = new double[h][];
		for ( int r = 0; r < h; ++r )
			sys[r] = Arrays.copyOf( system[r], system[r].length );
		
		addPointToApproximateSystem(
				sys,
				x,
				y,
				color
		);
		
		// check condition on system here
		
		return -1;
	}
	/**
	 * creates new system.
	 * NB! the last row means the additional (free vars column)
	 * @param point
	 * @return
	 */
	public static double[][] createApproximateSystem(
			final int x,
			final int y,
			final int color
	) {
		double[][] system = new double[ApproximData.apprSysDim + 1][ApproximData.apprSysDim];
		addPointToApproximateSystem( system, x, y, color );
		return system;
	} // createApproximateSystem
	
	/**
	 * add point (row, column, color) to existing approximate system
	 * also reducts system to diag
	 * @param sys
	 * @param point
	 */
	public static void addPointToApproximateSystem(
			double[][] sys,
			final int x,
			final int y,
			final int color
	) {
		int eqNum, eqItemNum, I, J, i, j;
		int[] tmp;
		double[] eq;
		final int eqDim = approximateSystemPows.length;
		double[] freeVarsCol = sys[freeValsRowNum];
		// update system
		for ( eqNum = 0; eqNum < eqDim; ++eqNum )
		{
			tmp = approximateSystemPows[eqNum];
			I = tmp[0]; J = tmp[1];
			eq = sys[eqNum];
			// update equation
			for ( eqItemNum = 0; eqItemNum < eqDim; ++eqItemNum )
			{
				tmp = approximateSystemPows[eqItemNum];
				i = tmp[0]; j = tmp[1];
				int additVal = MathUtils.pow( x, I+i ) * MathUtils.pow( y, J+j );
				eq[eqItemNum] += additVal;
			} // for eqItemNum
			// update free vars column item
			freeVarsCol[eqNum] += MathUtils.pow( x, I ) * MathUtils.pow( y, J ) * color;
		} // for eqNum
		//sys = MathUtils.reductToDiag( sys );
	} // void addPointToApproximateSystem
	
	// consts
	public final static int freeValsRowNum = 6;
	private final static int[][] approximateSystemPows = new int[][] {
		new int[] { 0, 0 },
		new int[] { 0, 1 },
		new int[] { 0, 2 },
		new int[] { 1, 0 },
		new int[] { 1, 1 },
		new int[] { 2, 0 },
	};
	private final static byte[][] neighborsOffsets = new byte[][] {
		new byte[] { -1, -1 },
		new byte[] { -1, 0  },
		new byte[] { -1, 1  },
		new byte[] {  0, -1 },
	};
	// ~consts
}
